/*
 * Copyright 2012-2021 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.boot.orm.jpa;

import java.net.URL;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

import javax.sql.DataSource;

import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.persistenceunit.PersistenceUnitManager;
import org.springframework.orm.jpa.persistenceunit.PersistenceUnitPostProcessor;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

/**
 * 方便的 JPA EntityManagerFactory 实例构建器。构建时收集通用配置，
 * 然后允许通过流式构建器模式创建一个或多个 {@link LocalContainerEntityManagerFactoryBean}。
 * 构建器涵盖了最常用的选项，但如果需要更多控制，也可以在返回
 * {@code @Bean} 定义之前操作构建器的产物。
 *
 * @author Dave Syer
 * @author Phillip Webb
 * @author Stephane Nicoll
 * @since 1.3.0
 */
public class EntityManagerFactoryBuilder {

	private final JpaVendorAdapter jpaVendorAdapter;

	private final PersistenceUnitManager persistenceUnitManager;

	private final Map<String, Object> jpaProperties;

	private final URL persistenceUnitRootLocation;

	private AsyncTaskExecutor bootstrapExecutor;

	private PersistenceUnitPostProcessor[] persistenceUnitPostProcessors;

	/**
	 * 创建一个新实例，传入多个 EntityManagerFactory 实例创建时共享的公共部分。
	 * @param jpaVendorAdapter 供应商适配器
	 * @param jpaProperties 要传递给持久化提供者的 JPA 属性
	 * @param persistenceUnitManager 持久化单元信息的可选来源（可以为 null）
	 */
	public EntityManagerFactoryBuilder(JpaVendorAdapter jpaVendorAdapter, Map<String, ?> jpaProperties,
			PersistenceUnitManager persistenceUnitManager) {
		this(jpaVendorAdapter, jpaProperties, persistenceUnitManager, null);
	}

	/**
	 * 创建一个新实例，传入多个 EntityManagerFactory 实例创建时共享的公共部分。
	 * @param jpaVendorAdapter 供应商适配器
	 * @param jpaProperties 要传递给持久化提供者的 JPA 属性
	 * @param persistenceUnitManager 持久化单元信息的可选来源（可以为 null）
	 * @param persistenceUnitRootLocation 作为回退使用的持久化单元根位置（可以为 null）
	 * @since 1.4.1
	 */
	public EntityManagerFactoryBuilder(JpaVendorAdapter jpaVendorAdapter, Map<String, ?> jpaProperties,
			PersistenceUnitManager persistenceUnitManager, URL persistenceUnitRootLocation) {
		this.jpaVendorAdapter = jpaVendorAdapter;
		this.persistenceUnitManager = persistenceUnitManager;
		this.jpaProperties = new LinkedHashMap<>(jpaProperties);
		this.persistenceUnitRootLocation = persistenceUnitRootLocation;
	}

	public Builder dataSource(DataSource dataSource) {
		return new Builder(dataSource);
	}

	/**
	 * 配置 {@link LocalContainerEntityManagerFactoryBean} 使用的引导执行器。
	 * @param bootstrapExecutor 执行器
	 * @since 2.1.0
	 */
	public void setBootstrapExecutor(AsyncTaskExecutor bootstrapExecutor) {
		this.bootstrapExecutor = bootstrapExecutor;
	}

	/**
	 * 设置应用于用于创建 {@link LocalContainerEntityManagerFactoryBean} 的 PersistenceUnitInfo 的
	 * {@linkplain PersistenceUnitPostProcessor 持久化单元后置处理器}。
	 * @param persistenceUnitPostProcessors 要使用的持久化单元后置处理器
	 * @since 2.5.0
	 */
	public void setPersistenceUnitPostProcessors(PersistenceUnitPostProcessor... persistenceUnitPostProcessors) {
		this.persistenceUnitPostProcessors = persistenceUnitPostProcessors;
	}

	/**
	 * 用于构建 LocalContainerEntityManagerFactoryBean 的流式构建器。
	 */
	public final class Builder {

		private DataSource dataSource;

		private String[] packagesToScan;

		private String persistenceUnit;

		private Map<String, Object> properties = new HashMap<>();

		private String[] mappingResources;

		private boolean jta;

		private Builder(DataSource dataSource) {
			this.dataSource = dataSource;
		}

		/**
		 * 用于扫描 {@code @Entity} 注解的包名列表。
		 * @param packagesToScan 要扫描的包
		 * @return 用于链式调用的构建器
		 */
		public Builder packages(String... packagesToScan) {
			this.packagesToScan = packagesToScan;
			return this;
		}

		/**
		 * 其所在包将被扫描以寻找 {@code @Entity} 注解的类。
		 * @param basePackageClasses 用于确定包的类
		 * @return 用于链式调用的构建器
		 */
		public Builder packages(Class<?>... basePackageClasses) {
			Set<String> packages = new HashSet<>();
			for (Class<?> type : basePackageClasses) {
				packages.add(ClassUtils.getPackageName(type));
			}
			this.packagesToScan = StringUtils.toStringArray(packages);
			return this;
		}

		/**
		 * 持久化单元的名称。如果只构建一个 EntityManagerFactory 可省略该名称，
		 * 但如果在同一应用中有多个，应赋予不同名称。
		 * @param persistenceUnit 持久化单元名称
		 * @return 用于链式调用的构建器
		 */
		public Builder persistenceUnit(String persistenceUnit) {
			this.persistenceUnit = persistenceUnit;
			return this;
		}

		/**
		 * 标准 JPA 或特定供应商配置的通用属性。这些属性会覆盖构造函数中提供的任何值。
		 * @param properties 要使用的属性
		 * @return 用于链式调用的构建器
		 */
		public Builder properties(Map<String, ?> properties) {
			this.properties.putAll(properties);
			return this;
		}

		/**
		 * 持久化单元的映射资源（相当于 {@code persistence.xml} 中的 {@code <mapping-file>} 条目）。
		 * <p>
		 * 注意映射资源必须是相对于类路径根的路径，例如 "META-INF/mappings.xml" 或 "com/mycompany/repository/mappings.xml"，
		 * 以便可以通过 {@code ClassLoader.getResource()} 加载。
		 * @param mappingResources 要使用的映射资源
		 * @return 用于链式调用的构建器
		 */
		public Builder mappingResources(String... mappingResources) {
			this.mappingResources = mappingResources;
			return this;
		}

		/**
		 * 配置是否使用 JTA {@link DataSource}，即是否应调用
		 * {@link LocalContainerEntityManagerFactoryBean#setDataSource(DataSource) setDataSource}
		 * 或
		 * {@link LocalContainerEntityManagerFactoryBean#setJtaDataSource(DataSource) setJtaDataSource}
		 * 在 {@link LocalContainerEntityManagerFactoryBean} 上。
		 * @param jta 数据源是否为 JTA
		 * @return 用于链式调用的构建器
		 */
		public Builder jta(boolean jta) {
			this.jta = jta;
			return this;
		}

		public LocalContainerEntityManagerFactoryBean build() {
			LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
			if (EntityManagerFactoryBuilder.this.persistenceUnitManager != null) {
				entityManagerFactoryBean
						.setPersistenceUnitManager(EntityManagerFactoryBuilder.this.persistenceUnitManager);
			}
			if (this.persistenceUnit != null) {
				entityManagerFactoryBean.setPersistenceUnitName(this.persistenceUnit);
			}
			entityManagerFactoryBean.setJpaVendorAdapter(EntityManagerFactoryBuilder.this.jpaVendorAdapter);

			if (this.jta) {
				entityManagerFactoryBean.setJtaDataSource(this.dataSource);
			}
			else {
				entityManagerFactoryBean.setDataSource(this.dataSource);
			}
			entityManagerFactoryBean.setPackagesToScan(this.packagesToScan);
			entityManagerFactoryBean.getJpaPropertyMap().putAll(EntityManagerFactoryBuilder.this.jpaProperties);
			entityManagerFactoryBean.getJpaPropertyMap().putAll(this.properties);
			if (!ObjectUtils.isEmpty(this.mappingResources)) {
				entityManagerFactoryBean.setMappingResources(this.mappingResources);
			}
			URL rootLocation = EntityManagerFactoryBuilder.this.persistenceUnitRootLocation;
			if (rootLocation != null) {
				entityManagerFactoryBean.setPersistenceUnitRootLocation(rootLocation.toString());
			}
			if (EntityManagerFactoryBuilder.this.bootstrapExecutor != null) {
				entityManagerFactoryBean.setBootstrapExecutor(EntityManagerFactoryBuilder.this.bootstrapExecutor);
			}
			if (EntityManagerFactoryBuilder.this.persistenceUnitPostProcessors != null) {
				entityManagerFactoryBean.setPersistenceUnitPostProcessors(
						EntityManagerFactoryBuilder.this.persistenceUnitPostProcessors);
			}
			return entityManagerFactoryBean;
		}

	}

}
